package com.aibaixun.common.utils;

import com.aibaixun.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

@Slf4j
public class SignUtils {
    @Autowired
    private RedisTemplate redisTemplate;
    /** * 校验Header上的参数-验证是否传入值 * <p> * 有个很重要的一点，就是对此请求进行时间验证，如果大于10分钟表示此链接已经超时，防止别人来到这个链接去请求。这个就是防止盗链。 * * @param request * @return */
    public static boolean verifyHeaderParams(HttpServletRequest request) {
        //应用id
        String appId = request.getHeader(Constants.APP_ID);
        if (StringUtils.isEmpty(appId)) {
            return false;
        }
        //时间戳,增加链接的有效时间,超过阈值,即失效
        String timeStamp = request.getHeader(Constants.TIME_STAMP);
        if (StringUtils.isEmpty(timeStamp)) {
            return false;
        }
        //调用方传递的签名
        String signature = request.getHeader(Constants.SIGN);
        if (StringUtils.isEmpty(signature)) {
            return false;
        }
        //毫秒
        long diff = System.currentTimeMillis() - Long.parseLong(timeStamp);
        //大于10分钟
//        if (diff > 1000 * 60 * 10) {
//            return false;
//        }
        return true;
    }

    /**
     * 将所有请求参数与应用密钥appSecret进行排序加密后生成签名(signature) 然后与 调用方法传递的签名(signature)进行比较
     * @param params 根据key升序排序的后所有请求参数
     * @param appSecret 应用id对应的应用密钥
     * @return 签名比较结构 true为签名正确
     */
    public static boolean verifySignature(SortedMap<String, String> params, String appSecret) {
        //调用方传过来的签名
        String paramSignature = params.get(Constants.SIGN);
        log.info("调用方传过来的Sign:{}", paramSignature);
        if (params == null || StringUtils.isEmpty(appSecret)) {
            return false;
        }
        //将调用方的请求参数 与 应用密钥 按签名规则处理后生成的签名
        String signature = generateSignature(params, appSecret);
        log.info("后端生成的Sign:{}", signature);
        //比较调用方传的签名 与 后台生成的签名
        return StringUtils.isNotEmpty(signature) && StringUtils.equals(paramSignature, signature);
    }

//    public static void main(String[] args) {
//        String secret = "asd123fhg3b7fgh7dfg";
//
//        String appid = "gs001";
//        String timeStamp = String.valueOf(System.currentTimeMillis());
//        String sign = "";
//        System.out.println(System.currentTimeMillis());
//
//        SortedMap<String, String> sortedMap = new TreeMap<>();
//        sortedMap.put(Constants.APP_ID,appid);
//        sortedMap.put(Constants.TIME_STAMP,timeStamp);
//        sortedMap.put("username","gaszjf");
//        sortedMap.put("password","gaszjf@123");
//
//        System.out.println(generateSignature(sortedMap, secret));
//
//    }

    /**
     * 所有的参数与应用密钥appSecret 进行排序加密后生成签名
     * @param sortedMap 根据key升序排序的后所有请求参数
     * @param appSecret 应用id对应的应用密钥
     * @return 生成接口签名
     */
    public static String generateSignature(SortedMap<String, String> sortedMap, String appSecret) {
        //先要去掉 前端求参数传过来的 里的 signature
        sortedMap.remove(Constants.SIGN);
        //进行key,value拼接
        // e.g "key1value1key2value2"
        StringBuilder plainText = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
            plainText.append(entry.getKey()+ entry.getValue());
        }
        //拼接应用密钥 appSecret
        plainText.append(appSecret);
        //摘要
        String digest = plainText.toString();
        //将digest 转换成UTF-8 的 byte[] 后 使用MD5算法加密，最后将生成的md5字符串转换成大写
        try {
            return DigestUtils.md5Hex(StringUtils.getBytes(digest, "UTF-8")).toUpperCase();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 上面的流程中，会有个额外的安全处理，防止盗链，我们可以让链接有失效时间
     * 而利用nonce参数，可以防止重复提交,在签名验证成功后，判断是否重复提交,原理就是结合redis，判断是否已经提交过
     * @param appId 应用id
     * @param timeStamp 13位时间戳 * @param nonce 临时流水号/随机串 ,至少为10位 ，有效期内防重复提交
     * @param signature 接口签名
     * @return 是否重复请求
     */
    public boolean isReplayAttack(String appId, String timeStamp, String nonce, String signature) {
        StringBuilder redisKey = new StringBuilder();
        redisKey.append("IS_REPLAY_ATTACK").append(":")
            .append(Constants.APP_ID).append(":").append(appId)
            .append(Constants.TIME_STAMP).append(":").append(timeStamp)
            .append(Constants.NONCE).append(":").append(nonce)
            .append(Constants.SIGN).append(":").append(signature);
        Object value = redisTemplate.opsForValue().get(redisKey);
        if (value != null && StringUtils.equals(signature, value.toString()))
            return false;
        else
            redisTemplate.opsForValue().set(redisKey, signature, 1000 * 50);
        return false;
    }
}
